﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        #region Pcm转Mu律

        /// <summary>
        /// pcm数据文件转换为Mu律数据文件
        /// </summary>
        /// <param name="pcmFilePath">带头的pcm数据文件路径</param>
        /// <param name="muLawFilePath">Mu律数据文件存储路径</param>
        public static void PcmToMuLaw(string pcmFilePath, string muLawFilePath)
        {
            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            if (!File.Exists(pcmFilePath))
            {
                throw new FileNotFoundException("带头的pcm数据文件路径不存在", pcmFilePath);
            }

            if (string.IsNullOrEmpty(muLawFilePath))
            {
                throw new ArgumentNullException(nameof(muLawFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo.SampleBits != 16)
                {
                    throw new InvalidOperationException($"仅支持16位采样位数的pcm数据文件转换为Mu律数据文件,当前数据采样位数为{waveInfo.SampleBits}");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Pcm);
                var srcDataPtr = new UnmanagedMemoryPointer<short>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(short));
                disposabler.Add(srcDataPtr, 0);

                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitivePcmToMuLaw(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(muLawFilePath);
                var destFileStream = new FileStream(muLawFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.MuLaw);
                destFileStream.Write(destDataPtr.GetSpan());

                FillALawAndMuLawHead(destFileStream, WaveEncoding.MuLaw, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length);
            }
        }

        /// <summary>
        /// pcm数据转换为Mu律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <returns>Mu律数据</returns>
        public static byte[] PcmToMuLaw(short[] pcmData)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            byte[] muLawData = new byte[pcmData.Length];
            fixed (byte* muLawDataPtr = muLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToMuLaw(pcmDataPtr, muLawDataPtr, muLawData.Length);
                }
            }
            return muLawData;
        }

        /// <summary>
        /// pcm数据转换为Mu律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="muLawDataIndex">Mu律数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void PcmToMuLaw(short[] pcmData, int pcmDataIndex, byte[] muLawData, int muLawDataIndex, int length)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            if (muLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(muLawDataIndex), "Mu律数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (muLawDataIndex + length > muLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Mu律数据起始索引\"加\"转换数据长度\"大于了\"Mu律数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* muLawDataPtr = muLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToMuLaw(pcmDataPtr + pcmDataIndex, muLawDataPtr + muLawDataIndex, length);
                }
            }
        }

        /// <summary>
        /// pcm数据转换为Mu律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="muLawData">Mu律数据</param>
        public static void PcmToMuLaw(Span<short> pcmData, Span<byte> muLawData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* muLawDataPtr = muLawData.DataPtr;
            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* muLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref muLawData[0]);
#endif
            PrimitivePcmToMuLaw(pcmDataPtr, muLawDataPtr, Math.Min(pcmData.Length, muLawData.Length));
        }

        /// <summary>
        /// pcm数据转换为Mu律数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="muLawData">Mu律数据</param>
        public static void PcmToMuLaw(SpanZ<short> pcmData, SpanZ<byte> muLawData)
        {
            PrimitivePcmToMuLaw(pcmData.DataPtr, muLawData.DataPtr, Math.Min(pcmData.Length, muLawData.Length));
        }

        private static void PrimitivePcmToMuLaw(short* pcmDataPtr, byte* muLawDataPtr, int length)
        {
            CheckALawSegEnd();

            int mask;
            int seg;
            byte uVal;
            short pcmVal;

            for (int i = 0; i < length; i++)
            {
                pcmVal = pcmDataPtr[i];

                if (pcmVal < 0)
                {
                    pcmVal = (short)(Bias - pcmVal);
                    mask = 0x7F;
                }
                else
                {
                    pcmVal += Bias;
                    mask = 0xFF;
                }

                seg = Search(pcmVal, 8);

                if (seg >= 8)
                {
                    muLawDataPtr[i] = (byte)(0x7F ^ mask);
                }
                else
                {
                    uVal = (byte)((seg << SegShift) | ((pcmVal >> (seg + 3)) & QuantMask));
                    muLawDataPtr[i] = (byte)(uVal ^ mask);
                }

            }
        }

        #endregion

        #region Mu律转pcm

        /// <summary>
        /// Mu律数据文件转换到pcm数据文件
        /// </summary>
        /// <param name="muLawFilePath">带头的Mu律数据文件路径</param>
        /// <param name="pcmFilePath">pcm数据文件存储路径</param>
        public static void MuLawToPcm(string muLawFilePath, string pcmFilePath)
        {
            if (string.IsNullOrEmpty(muLawFilePath))
            {
                throw new ArgumentNullException(nameof(muLawFilePath));
            }

            if (!File.Exists(muLawFilePath))
            {
                throw new FileNotFoundException("带头的Mu律数据文件不存在", muLawFilePath);
            }

            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(muLawFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.MuLaw);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<short>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitiveMuLawToPcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcmFilePath);
                var destFileStream = new FileStream(pcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Pcm);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.Pcm, DataSampleBits.BIT16, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length * sizeof(short));
            }
        }

        /// <summary>
        /// Mu律数据转换为pcm数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <returns>pcm数据</returns>
        public static short[] MuLawToPcm(byte[] muLawData)
        {
            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            short[] pcmData = new short[muLawData.Length];

            fixed (byte* muLawDataPtr = muLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveMuLawToPcm(muLawDataPtr, pcmDataPtr, muLawData.Length);
                }
            }

            return pcmData;
        }

        /// <summary>
        /// Mu律数据转换为pcm数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="muLawDataIndex">Mu律数据起始索引</param>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void MuLawToPcm(byte[] muLawData, int muLawDataIndex, short[] pcmData, int pcmDataIndex, int length)
        {
            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (muLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(muLawDataIndex), "Mu律数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (muLawDataIndex + length > muLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Mu律数据起始索引\"加\"转换数据长度\"大于了\"Mu律数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* muLawDataPtr = muLawData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveMuLawToPcm(muLawDataPtr + muLawDataIndex, pcmDataPtr + pcmDataIndex, length);
                }
            }
        }

        /// <summary>
        /// Mu律数据转换为pcm数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void MuLawToPcm(Span<byte> muLawData, Span<short> pcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* muLawDataPtr = muLawData.DataPtr;            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* muLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref muLawData[0]);
#endif
            PrimitiveMuLawToPcm(muLawDataPtr, pcmDataPtr, Math.Min(muLawData.Length, pcmData.Length));
        }

        /// <summary>
        /// Mu律数据转换为pcm数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void MuLawToPcm(SpanZ<byte> muLawData, SpanZ<short> pcmData)
        {
            PrimitiveMuLawToPcm(muLawData.DataPtr, pcmData.DataPtr, Math.Min(muLawData.Length, pcmData.Length));
        }

        private static void PrimitiveMuLawToPcm(byte* muLawDataPtr, short* pcmDataPtr, int length)
        {
            int temp;
            byte uVal;

            for (int i = 0; i < length; i++)
            {
                uVal = muLawDataPtr[i];
                uVal = (byte)~uVal;
                temp = (uVal & QuantMask) << 3;
                temp += Bias;
                temp <<= ((uVal & SegMask) >> SegShift);

                pcmDataPtr[i] = (short)((uVal & SignBit) != 0 ? (Bias - temp) : (temp - Bias));
            }
        }

        #endregion
    }
}
